;;;;
;;; Particle Effect Examples (NeHe Lesson #19)
(in-package :xlui)

(defparameter *max-particles* 1000)
(defparameter *rainbow-colors*
  '((1.0 0.5 0.5) (1.0 0.75 0.5) (1.0 1.0 0.5) (0.75 1.0 0.5)
    (0.5 1.0 0.5) (0.5 1.0 0.75) (0.5 1.0 1.0) (0.5 0.75 1.0)
    (0.5 0.5 1.0) (0.75 0.5 1.0) (1.0 0.5 1.0) (1.0 0.5 0.75)))
        
(defclass particle-view (agent-3d-view)
  ((rainbow-p :accessor rainbow-p :initarg :rainbow :initform nil))
  (:documentation "NEHE Lesson 19: Particle Effects"))

(defmethod init ((view particle-view))
  (glShadeModel GL_SMOOTH)
  (glClearDepth 1.0d0)
  (glDisable GL_DEPTH_TEST)
  (glEnable gl_blend)
  (glBlendFunc GL_SRC_ALPHA GL_ONE)
  (glHint GL_PERSPECTIVE_CORRECTION_HINT GL_NICEST)
  (glHint GL_POINT_SMOOTH_HINT GL_NICEST)
  (glEnable GL_TEXTURE_2D))

(defstruct gl-particle
  active
  life 
  fade ;; Life and fade control how long particle is displayed and how brightly it appears.
  r g b
  x y z ;; position
  xi yi zi ;; direction
  xg yg zg) ;; Gravity

(defclass particle-effect (agent-3d)
  ((particles :accessor particles :initarg :particles :initform nil)
   (max-particles :accessor max-particles :initarg :max-particles :initform 1000
                  :documentation "The number of particles used to produce the effect.")
   (colors :accessor colors :initarg :colors :initform *rainbow-colors*
           :documentation "Color(s) to use for the particles in the effect.  If a list of
           colors is specified, then one is selected randomly for each particle.
           At the moment a color is specified as a list of three elements (r g b).")
   (initial-gravity :accessor initial-gravity :initarg :initial-gravity :initform '(0.0 -0.8 0.0)
                     :documentation "Directing force applied to particle.")
   (max-velocity :accessor max-velocity :initarg :max-velocity :initform 50.0
                 :documentation "Maximum velocity given to particles as they created or regenerated.")
   (default-velocity-func :accessor default-velocity-func :initarg :default-velocity-func
     :initform #'(lambda (dir vm) (declare (ignore dir)) (* (- (random vm) (/ vm 2.0)) 10.0))
     :documentation "This function specifies the initial velocity each particle will have when it is
     created or regenerated.  The function accepts two parameters: the direction component being specified, and
     the max value possible for that direction."))
  (:documentation ""))

(defmethod initialize-instance :after ((self particle-effect) &rest initargs)
  (declare (ignore initargs))
  (with-slots (particles max-particles max-velocity initial-gravity colors default-velocity-func) self
    (let ((n (length colors)))
      (setf particles (make-array max-particles))
      (dotimes (i max-particles)
        (setf (svref particles i) (make-gl-particle :active t
                                                    :life 1.0
                                                    :fade (+ (/ (random 100) 1000.0) 0.003)
                                                    :r (first (nth (floor (* i (/ n max-particles)))
                                                                   colors))
                                                    :g (second (nth (floor (* i (/ n max-particles)))
                                                                    colors))
                                                    :b (third (nth (floor (* i (/ n max-particles)))
                                                                   colors))
                                                    :x (x self)
                                                    :y (y self)
                                                    :z (z self)
                                                    :xi (funcall default-velocity-func 'xi max-velocity)
                                                    :yi (funcall default-velocity-func 'yi max-velocity)
                                                    :zi (funcall default-velocity-func 'zi max-velocity)
                                                    :xg (first initial-gravity)
                                                    :yg (second initial-gravity)
                                                    :zg (third initial-gravity)))))))
  
(defmethod draw ((self particle-effect))
  (with-slots (particles) self
    (loop for particle across particles
          when (gl-particle-active particle)
          do (let ((x (gl-particle-x particle))
                   (y (gl-particle-y particle))
                   (z (gl-particle-z particle)))
               (glColor4f (gl-particle-r particle)
                          (gl-particle-g particle)
                          (gl-particle-b particle)
                          (gl-particle-life particle))
               (glBegin GL_TRIANGLE_STRIP)
	       (glTexCoord2f 1.0 1.0) (glVertex3f (+ x 0.5) (+ y 0.5) z)
	       (glTexCoord2f 0.0 1.0) (glVertex3f (- x 0.5) (+ y 0.5) z)
	       (glTexCoord2f 1.0 0.0) (glVertex3f (+ x 0.5) (- y 0.5) z)
	       (glTexCoord2f 0.0 0.0) (glVertex3f (- x 0.5) (- y 0.5) z)
	       (glEnd)))))

(defun animate-particle (particle dt)
  (incf (gl-particle-x particle) (/ (gl-particle-xi particle) 2000.0))
  (incf (gl-particle-y particle) (/ (gl-particle-yi particle) 2000.0))
  (incf (gl-particle-z particle) (/ (gl-particle-zi particle) 2000.0))
  (incf (gl-particle-xi particle) (gl-particle-xg particle))
  (incf (gl-particle-yi particle) (gl-particle-yg particle))
  (incf (gl-particle-zi particle) (gl-particle-zg particle))
  (decf (gl-particle-life particle) (* (gl-particle-fade particle) dt))
  (when (< (gl-particle-life particle) 0.0)
    (setf (gl-particle-life particle) 1.0
          (gl-particle-fade particle) (+ (/ (random 100) 1000.0) 0.003)
          (gl-particle-x particle) 0.0 (gl-particle-y particle) 0.0 
          (gl-particle-z particle) 0.0
          (gl-particle-xi particle) (- (random 60) 30.0)
          (gl-particle-yi particle) (- (random 60) 30.0)
          (gl-particle-zi particle) (- (random 60) 30.0))))
  
	       
(defmethod animate ((agent particle-effect) dt)
  (loop for p across (particles agent)
        do (animate-particle p dt)))

(defparameter *particle-example*
  <application-window title = "NEHE Lesson 19, Particle Effects" margin="0">
   <particle-view name="particle-example">
    <camera eye-z="10.0"/>
    <particle-effect/>
   </particle-view>
</application-window>)